package org.rasterfun.language2

import collection.immutable.HashSet

/**
 * Something that it is possible to generate source for.
 * 
 * @author Hans Haggstrom
 */
// TODO: Move temporary mutable work data into the context instead.
// TODO: Extract the count usages functionality to some scala DSL utility library for rasterfun.
trait Compilable {

  /**
   * Number of different parts of the code that use this piece of code.
   */
  private def usages = if (alwaysInline) 1 else if(users == null) 0 else users.size

  private var variableName: String = null

  @transient
  private var users: Set[Int] = null

  protected def alwaysInline = false

  def children: List[Compilable] = Nil
  
  def makeCode(): String = {
    // Count number of usages
    clearUsages()
    countUsages(hashCode)

    // Generate actual code
    code()
  }

  def countUsage(user: Int) {
    if (users==null) users = HashSet(user)
    else users = users + user
  }

  def countUsages(user: Int) {
    countUsage(user)
    val childUser = this.hashCode
    children.zipWithIndex foreach { case (child, index) => child.countUsages(childUser + index) }
  }

  def clearUsages() {
    users = null
    variableName = null
    children foreach {_.clearUsages()}
  }

  def checkTyping(context  : Context, errorListener: String=>Unit)


  def generateCode(context  : Context): String = {
    val s = new StringBuilder
    generateCode(context, s)
    s.toString
  }

  def generateCode( context  : Context, s : StringBuilder )

  def code(context: Context, s : StringBuilder) {
    if (usages > 1) {
      if (variableName == null) {
        // Assign generated content to final variable defined just before this statement
        val t = new StringBuilder()
        generateCode(context, t)
        variableName = context.addVariable(t.toString())
      }

      // Use value calculated for variable
      s.append(" ")
      s.append(variableName)
      s.append(" ")
    }
    else {
      // Code used just once
      generateCode(context, s)
    }
  }

  def code(): String = {
    val s = new StringBuilder()
    val context = new Context()
    code(context, s)
    context.toString + s.toString
  }

  override def toString: String = makeCode()

}

